% validates a email address acording to rfc2822 (domain-literal is missing, as are CFWS tokens)
-module(bmula_rfc2822).
-include("bmula.inc").

-export(['addr-spec'/1, 'dot-atom'/1, 'dot-atom-text'/1, 'quoted-string'/1, test/0]).

'quoted-string'(B) ->
    Continue = fun(O, C) ->
        <<Pre:O/binary, Post/binary>> = B,
        case Post of
            <<$\\, _/binary>> ->
                C(O + 2, C);
            <<X, _/binary>> when X >= 1, X =< 8; X >= 14, X =< 32; X >= 35, X =< 91; X >= 93, X =< 126; X == 11; X == 12; X == 127; X == 33 -> 
                C(O + 1, C);
            _ ->
                {Pre, Post}
        end
    end,
    try
        {<<$", _/binary>> = R1, <<$", R2/binary>>} = Continue(1, Continue),
        {<<R1/binary, $">>, R2}
    catch
        _:_ ->
            false
    end.

'dot-atom'(B) -> 'dot-atom-text'(B).
'dot-atom-text'(B) ->
    Continue = fun(O, C) ->
        <<Pre:O/binary, Post/binary>> = B,
        case Post of
            <<X, _/binary>> when X >= $a, X =< $z; X >= $A, X =< $Z; X >= $0, X =< $9; X == $!; X == $#; X == $$; X == $%; X == $&; X == $'; X == $*; X == $+; X == $-; X == $/; X == $=; X == $?; X == $^; X == $_; X == $`; X == ${; X == $|; X == $}; X == $~ -> 
                C(O + 1, C);
            _ ->
                {Pre, Post}
        end
    end,
    case Continue(0, Continue) of
        {<<>>, _} ->
            false;
        {Pre1, <<$., Post1/binary>>} ->
            {Pre2, Post2} = 'dot-atom-text'(Post1),
            {<<Pre1/binary, $., Pre2/binary>>, <<Post2/binary>>};
        X ->
            X
    end.

'addr-spec'(Address) ->
    try
        {Local, <<$@, Post/binary>>} = case 'dot-atom'(Address) of
            false -> 'quoted-string'(Address);
            X -> X
        end,
        {Domain, <<>>} = 'dot-atom'(Post),
        {Local, Domain}
    catch
        _:_ ->
            false
    end.

test() ->
    D = <<"example.com">>,
    A = fun(L) ->
        Lb = list_to_binary(L),
        io:format("trying ~p ", [Lb]),
        R = 'addr-spec'(Lb),
        io:format("~p~n", [R]),
        R
    end,
    {<<"abc">>, D} = A("abc@example.com"),
    {<<"Abc">>, D} = A("Abc@example.com"),
    {<<"aBC">>, D} = A("aBC@example.com"),
    {<<"abc.123">>, D} = A("abc.123@example.com"),
    {<<"1234567890">>, D} = A("1234567890@example.com"),
    {<<"_______">>, D} = A("_______@example.com"),
    {<<"abc+mailbox/department=shipping">>, D} = A("abc+mailbox/department=shipping@example.com"),
    {<<"!#$%&'*+-/=?^_`.{|}~">>, D} = A("!#$%&'*+-/=?^_`.{|}~@example.com"),
    {<<$", "abc@def", $">>, D} = A("\"abc@def\"@example.com"),
    {<<"\"Fred \\\"quota\\\" Bloggs\"">>, D} = A("\"Fred \\\"quota\\\" Bloggs\"@example.com"),

    false = A("Abc.example.com"),
    false = A("Abc.@example.com"),
    false = A("Abc..123@example.com"),
    false = A("A@b@c@example.com"),
    false = A("()[]\;:,<>@example.com"),

    ok. 
